﻿using DotNetty.Buffers;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System.Net;

namespace DotNetty.Extensions
{
    public class UdpSocket : IAsyncDisposable
    {
        public UdpSocket(int port = 0, IPAddress ipAddress = null)
        {
            _port = port;
            _ipAddress = ipAddress;
            _ipAddress ??= IPAddress.Any;
        }

        private readonly int _port;

        private readonly IPAddress _ipAddress;

        private readonly SemaphoreSlim _slim = new(1, 1);

        private bool _slimDispose = false;

        public int WorkerGroupCount { get; set; } = 0;

        private IEventLoopGroup group;

        public IChannel Channel { get; internal set; }

        public event Action<Bootstrap> OnCreateBootstrap;

        public event Action<IChannelPipeline> OnChannelPipeline;

        public event Action OnStarting;

        public event Action OnStarted;

        public event Action<Exception> OnStopped;

        public event Action<Exception> OnException;

        public event Action<EndPoint, byte[]> OnMessage;

        internal void Ex(Exception ex)
        {
            try
            {
                OnException?.Invoke(ex);
            }
            catch { }
        }

        internal void Message(EndPoint endPoint, byte[] bytes)
        {
            try
            {
                OnMessage?.Invoke(endPoint, bytes);
            }
            catch { }
        }

        private async Task CloseGroupAsync()
        {
            if (group != null)
            {
                try
                {
                    await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
                }
                catch { }
            }
        }

        private async Task StartBaseAsync()
        {
            try
            {
                if (WorkerGroupCount <= 0)
                {
                    group = new MultithreadEventLoopGroup();
                }
                else
                {
                    group = new MultithreadEventLoopGroup(WorkerGroupCount);
                }

                var bootstrap = new Bootstrap();
                bootstrap
                    .Group(group)
                    .Channel<SocketDatagramChannel>()
                    .Option(ChannelOption.SoBroadcast, true) //允许广播
                    .Handler(new ActionChannelInitializer<IChannel>(ch =>
                    {
                        IChannelPipeline pipeline = ch.Pipeline;
                        OnChannelPipeline?.Invoke(pipeline);
                        pipeline.AddLast(new UdpHandler(this));
                    }));

                OnCreateBootstrap?.Invoke(bootstrap);

                try
                {
                    OnStarting?.Invoke();
                }
                catch { }

                Channel = await bootstrap.BindAsync(_ipAddress, _port);

                try
                {
                    OnStarted?.Invoke();
                }
                catch { }

            }
            catch (Exception ex)
            {
                await CloseGroupAsync();

                try
                {
                    OnStopped?.Invoke(ex);
                }
                catch { }
            }
        }

        public async Task StartAsync()
        {
            if (_slimDispose)
            {
                return;
            }

            try
            {
                var ok = await _slim.WaitAsync(0);

                if (!ok) //等不到信号量，表示被停止了，直接返回
                {
                    return;
                }

                await StartBaseAsync();

                _slim.Release();
            }
            catch { }
        }

        public async Task WriteAndFlushAsync(EndPoint endPoint, IByteBuffer buffer)
        {
            var dp = new DatagramPacket(buffer, endPoint);
            await Channel.WriteAndFlushAsync(dp);
        }

        public async Task WriteAndFlushAsync(EndPoint endPoint, byte[] bytes)
        {
            var buffer = Unpooled.WrappedBuffer(bytes);
            await WriteAndFlushAsync(endPoint, buffer);
        }

        public async Task WriteAndFlushAsync(string ipPort, IByteBuffer buffer)
        {
            var dp = new DatagramPacket(buffer, ipPort.ToIPEndPoint());
            await Channel.WriteAndFlushAsync(dp);
        }

        public async Task WriteAndFlushAsync(string ipPort, byte[] bytes)
        {
            var buffer = Unpooled.WrappedBuffer(bytes);
            await WriteAndFlushAsync(ipPort, buffer);
        }

        public async ValueTask DisposeAsync()
        {
            await _slim.WaitAsync();
            _slimDispose = true;

            if (Channel != null)
            {
                try
                {
                    //关闭服务器通道
                    await Channel.CloseAsync();
                    Channel = null;
                }
                catch { }
            }

            await CloseGroupAsync();

            try
            {
                OnStopped?.Invoke(null);
            }
            catch { }

            _slim.Release();
            _slim.Dispose();

            GC.SuppressFinalize(this);
        }
    }
}
